// STL
#include <fstream>                                // std::ifstream, std::ofstream
#include <string>                                 // std::string
#include <vector>                                 // std::vector<>

// Boost
#include <boost/archive/text_iarchive.hpp>        // boost::archive::text_iarchive
#include <boost/archive/text_oarchive.hpp>        // boost::archive::text_oarchive

// jScience
#include "jScience/linalg.hpp"                    // Matrix<>

// this
#include "statsxx/machine_learning/DBN.hpp"       // machine_learning::DBN
#include "statsxx/machine_learning/NeuralNet.hpp" // NEURAL_NET
#include "statsxx/machine_learning/utility.hpp"   // machine_learning::DBN_to_NN_Wb()


//
// DESC: Creates a MLP (or reads one in).
//
NEURAL_NET create_MLP(
                      const bool              create,
                      // -----
                      const std::string       dbn_file,
                      // -----
                      const std::vector<int> &architecture,
                      const int               af_type,
                      const bool              is_classif,
                      // -----
                      const std::string       mlp_file
                      )
{
    // NOTE: I am not sure that these settings still (or currently) work:
    const bool fully_connect = false;
    const bool recurrent     = false;

    //=========================================================

    NEURAL_NET mlp;

    if(create)
    {
        if( !dbn_file.empty() )
        {
            // TODO: Should somehow automatically extract and assign activation functions from DBN

            machine_learning::DBN dbn;

            // READ DBN FROM ARCHIVE
            {
                std::ifstream ifs(dbn_file);

                boost::archive::text_iarchive ia(ifs);

                ia >> dbn;
            }

            // GET ARCHITECTURE AND WEIGHTS OF DBN
            std::vector<int> dbn_architecture = dbn.architecture();

            std::vector<Matrix<double>> W_DBN = dbn.get_W();
            std::vector<Vector<double>> b_DBN = dbn.get_b();

            // CONVERT ARCHITECTURE AND WEIGHTS FOR NN
            dbn_architecture.push_back(architecture[0]);

            Matrix<double> W_NN;
            Vector<double> b_NN;
            std::tie(
                     W_NN,
                     b_NN
                     ) = machine_learning::DBN_to_NN_Wb(
                                                        architecture[0],
                                                        W_DBN,
                                                        b_DBN
                                                        );

            // CREATE MLP
            mlp.create_MLP(
                           dbn_architecture,
                           recurrent,
                           af_type,
                           is_classif,
                           W_NN,
                           b_NN
                           );
        }
        else
        {
            std::vector<int> nhn = architecture;
            nhn.erase(nhn.begin());
            nhn.pop_back();

            mlp.create_MLP(
                           architecture.front(),
                           architecture.back(),
                           nhn.size(),
                           nhn,
                           fully_connect,
                           recurrent,
                           af_type,
                           is_classif
                           );
        }

        // SAVE DBN TO ARCHIVE
        {
            std::ofstream ofs(mlp_file);

            boost::archive::text_oarchive oa(ofs);

            oa << mlp;
        }
    }
    else
    {
        // READ DBN FROM ARCHIVE
        {
            std::ifstream ifs(mlp_file);

            boost::archive::text_iarchive ia(ifs);

            ia >> mlp;
        }
    }

    return mlp;
}
